#!/bin/sh
#
#
# Varnish
#
# Description:  Manage varnish instances as a HA resource
#
# Author:       Léon Keijser <keijser@stone-it.com>
#
# License:      GNU General Public License (GPL)
#
# See usage() for more details
#
# OCF instance parameters:
#   OCF_RESKEY_pid
#   OCF_RESKEY_binary
#   OCF_RESKEY_client_binary
#   OCF_RESKEY_config
#   OCF_RESKEY_name
#   OCF_RESKEY_listen_address
#   OCF_RESKEY_mgmt_address
#   OCF_RESKEY_ttl
#   OCF_RESKEY_varnish_user
#   OCF_RESKEY_varnish_group
#   OCF_RESKEY_backend_type
#   OCF_RESKEY_backend_size
#   OCF_RESKEY_backend_file
#   OCF_RESKEY_thread_pools
#   OCF_RESKEY_thread_pool_min
#   OCF_RESKEY_thread_pool_max
#   OCF_RESKEY_thread_pool_timeout
#   OCF_RESKEY_secret
#
#######################################################################
# Initialization:
: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs

#######################################################################
# Set default paramenter values

# Set these two first, as other defaults depend on it
OCF_RESKEY_name_default=${OCF_RESOURCE_INSTANCE}
: ${OCF_RESKEY_name=${OCF_RESKEY_name_default}}

OCF_RESKEY_config_default=""
OCF_RESKEY_binary_default=varnishd
OCF_RESKEY_client_binary_default=varnishadm
OCF_RESKEY_pid_default=/var/run/varnishd_${OCF_RESKEY_name}.pid
OCF_RESKEY_listen_address_default=0.0.0.0:80
OCF_RESKEY_ttl_default=600
OCF_RESKEY_varnish_user_default=varnish
OCF_RESKEY_varnish_group_default=varnish
OCF_RESKEY_backend_type_default=malloc
OCF_RESKEY_backend_size_default=1G
OCF_RESKEY_backend_file_default=/var/lib/varnish/${OCF_RESKEY_name}.bin
OCF_RESKEY_thread_pools_default=2
OCF_RESKEY_thread_pool_min_default=100
OCF_RESKEY_thread_pool_max_default=3000
OCF_RESKEY_thread_pool_timeout_default=120
OCF_RESKEY_maxfiles_default=131072
OCF_RESKEY_max_locked_memory_default=82000
OCF_RESKEY_secret_default=/etc/varnish/secret

: ${OCF_RESKEY_config=${OCF_RESKEY_config_default}}
: ${OCF_RESKEY_binary=${OCF_RESKEY_binary_default}}
: ${OCF_RESKEY_client_binary=${OCF_RESKEY_client_binary_default}}
: ${OCF_RESKEY_pid=${OCF_RESKEY_pid_default}}
: ${OCF_RESKEY_listen_address=${OCF_RESKEY_listen_address_default}}
: ${OCF_RESKEY_ttl=${OCF_RESKEY_ttl_default}}
: ${OCF_RESKEY_varnish_user=${OCF_RESKEY_varnish_user_default}}
: ${OCF_RESKEY_varnish_group=${OCF_RESKEY_varnish_group_default}}
: ${OCF_RESKEY_backend_type=${OCF_RESKEY_backend_type_default}}
: ${OCF_RESKEY_backend_size=${OCF_RESKEY_backend_size_default}}
: ${OCF_RESKEY_backend_file=${OCF_RESKEY_backend_file_default}}
: ${OCF_RESKEY_thread_pools=${OCF_RESKEY_thread_pools_default}}
: ${OCF_RESKEY_thread_pool_min=${OCF_RESKEY_thread_pool_min_default}}
: ${OCF_RESKEY_thread_pool_max=${OCF_RESKEY_thread_pool_max_default}}
: ${OCF_RESKEY_thread_pool_timeout=${OCF_RESKEY_thread_pool_timeout_default}}
: ${OCF_RESKEY_maxfiles=${OCF_RESKEY_maxfiles_default}}
: ${OCF_RESKEY_max_locked_memory=${OCF_RESKEY_max_locked_memory_default}}
: ${OCF_RESKEY_secret=${OCF_RESKEY_secret_default}}

meta_data() {
	cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="varnish" version="1.0">
<version>1.0</version>

<longdesc lang="en">
The Varnish Resource Agent can manage several varnishd
instances throughout the cluster. It does so by creating
a unique PID file and requires a unique listen address 
and name for each instance.
</longdesc>
<shortdesc lang="en">Manage a Varnish instance</shortdesc>

<parameters>

<parameter name="config" unique="1" required="1">
<longdesc lang="en">
The VCL configuration file that Varnish should manage, for example
"/etc/varnish/default.vcl".
</longdesc>
<shortdesc lang="en">VCL file</shortdesc>
<content type="string" default="${OCF_RESKEY_config_default}" />
</parameter>

<parameter name="name" unique="1">
<longdesc lang="en">
Override the name of the instance that should be given to Varnish
(defaults to the resource identifier).
</longdesc>
<shortdesc lang="en">Instance name</shortdesc>
<content type="string" default="${OCF_RESKEY_name_default}" />
</parameter>

<parameter name="pid" unique="1">
<longdesc lang="en">
Write the process's PID to the specified file.
The default will include the specified name, i.e.:
"/var/run/varnish_production.pid". Unlike what this help message shows,
it is most likely not necessary to change this parameter.
</longdesc>
<shortdesc lang="en">Listen address</shortdesc>
<content type="string" default="${OCF_RESKEY_pid_default}" />
</parameter>

<parameter name="listen_address" unique="1">
<longdesc lang="en">
Listen on this address:port, for example "192.168.1.1:80"
</longdesc>
<shortdesc lang="en">Listen address</shortdesc>
<content type="string" default="${OCF_RESKEY_listen_address_default}" />
</parameter>

<parameter name="mgmt_address" unique="1" required="1">
<longdesc lang="en">
Provide a management interface, for example "127.0.0.1:2222"
</longdesc>
<shortdesc lang="en">Management interface</shortdesc>
<content type="string" />
</parameter>

<parameter name="ttl">
<longdesc lang="en">
Specify a hard minimum time to live for cached documents.
</longdesc>
<shortdesc lang="en">TTL</shortdesc>
<content type="integer" default="${OCF_RESKEY_ttl_default}" />
</parameter>

<parameter name="varnish_user">
<longdesc lang="en">
Specify the name of an unprivileged user to which the 
child process should switch before it starts accepting 
connections.
</longdesc>
<shortdesc lang="en">Unprivileged user</shortdesc>
<content type="string" default="${OCF_RESKEY_varnish_user_default}" />
</parameter>

<parameter name="varnish_group">
<longdesc lang="en">
Specify the name of an unprivileged group to which 
the child process should switch before it starts accepting 
connections.
</longdesc>
<shortdesc lang="en">Unprivileged group</shortdesc>
<content type="string" default="${OCF_RESKEY_varnish_group_default}" />
</parameter>

<parameter name="backend_type">
<longdesc lang="en">
Use the specified storage backend. Valid options are
'malloc' for memory and 'file' for a file backend.
</longdesc>
<shortdesc lang="en">Backend type</shortdesc>
<content type="string" default="${OCF_RESKEY_backend_type_default}" />
</parameter>

<parameter name="backend_size">
<longdesc lang="en">
Specify the size of the backend. For example "1G".
</longdesc>
<shortdesc lang="en">Backend size</shortdesc>
<content type="string" default="${OCF_RESKEY_backend_size_default}" />
</parameter>

<parameter name="backend_file" unique="1">
<longdesc lang="en">
Specify the backend filename if you use backend_type file. 
For example /var/lib/varnish/mybackend.bin
</longdesc>
<shortdesc lang="en">Backend file</shortdesc>
<content type="string" default="${OCF_RESKEY_backend_file_default}" />
</parameter>

<parameter name="threads_pools">
<longdesc lang="en">
Number of worker thread pools.
Each pool has the minimum, maximum and timeout values configured in the
thread_pool_min, thread_pool_max and thread_pool_timeout parameters
</longdesc>
<shortdesc lang="en">Worker thread pools</shortdesc>
<content type="string" default="${OCF_RESKEY_thread_pools_default}" />
</parameter>

<parameter name="thread_pool_min">
<longdesc lang="en">
Start  at  least  min but no more than max worker 
threads with the specified idle timeout in each pool.
</longdesc>
<shortdesc lang="en">Minimum worker threads</shortdesc>
<content type="string" default="${OCF_RESKEY_thread_pool_min_default}" />
</parameter>

<parameter name="thread_pool_max">
<longdesc lang="en">
Start  at  least  min but no more than max worker 
threads with the specified idle timeout in each pool.
</longdesc>
<shortdesc lang="en">Maximum worker threads</shortdesc>
<content type="string" default="${OCF_RESKEY_thread_pool_max_default}" />
</parameter>

<parameter name="thread_pool_timeout">
<longdesc lang="en">
Start  at  least  min but no more than max worker 
threads with the specified idle timeout in each pool.
</longdesc>
<shortdesc lang="en">Worker threads timeout</shortdesc>
<content type="string" default="${OCF_RESKEY_thread_pool_timeout_default}" />
</parameter>

<parameter name="client_binary">
<longdesc lang="en">
This is used to control Varnish via a CLI. It's currently
only used to check the status of the running child process.
</longdesc>
<shortdesc lang="en">Varnish admin utility</shortdesc>
<content type="string" default="${OCF_RESKEY_client_binary_default}" />
</parameter>

<parameter name="maxfiles">
<longdesc lang="en">
Maximum number of open files (for ulimit -n)
</longdesc>
<shortdesc lang="en">Max open files</shortdesc>
<content type="string" default="${OCF_RESKEY_maxfiles_default}" />
</parameter>

<parameter name="max_locked_memory">
<longdesc lang="en">
Locked shared memory limit (for ulimit -l)
</longdesc>
<shortdesc lang="en">Max locked memory</shortdesc>
<content type="string" default="${OCF_RESKEY_max_locked_memory_default}" />
</parameter>

<parameter name="secret">
<longdesc lang="en">
Path to a file containing a secret used for authorizing access to the management port.
</longdesc>
<shortdesc lang="en">Path of the secret file</shortdesc>
<content type="string" default="${OCF_RESKEY_secret_default}" />
</parameter>

</parameters>

<actions>
<action name="start"        timeout="20s" />
<action name="stop"         timeout="20s" />
<action name="monitor"      timeout="20s" interval="10s" depth="0" />
<action name="status"       timeout="20s" />
<action name="meta-data"    timeout="5s" />
<action name="validate-all"   timeout="20s" />
</actions>
</resource-agent>
END
}

#######################################################################


varnish_usage() {
	cat <<END
usage: $0 {start|stop|monitor|validate-all|meta-data}

Expects to have a fully populated OCF RA-compliant environment set.
END
}

varnish_status() {
    local pid
    local rc

    # FAILED = pidfile exist, but no running proc (or mismatch pid)
    # SUCCES = contents of pidfile == running process id
    # NOTRUN = no pidfile, no running process

    # check if pidfile exists and larger than 0 bytes
    if [ -s $OCF_RESKEY_pid ]; then
        # it does, now check if the pid exists
        pid=$(cat $OCF_RESKEY_pid)
        ocf_run kill -s 0 $pid
        rc=$?
        if [ $rc -eq 0 ]; then
            ocf_log info "Varnish is running"
            # check if the child process is started and varnish is
            # reporting child status as ok
            ocf_run $OCF_RESKEY_client_binary -T $OCF_RESKEY_mgmt_address -S $OCF_RESKEY_secret status
            v_rc=$?
            if [ "$v_rc" -eq 0 ]; then
                ocf_log info "Varnish child reported running"
                return $OCF_SUCCESS
            else
                ocf_log err "Varnish child not running"
                return $OCF_ERR_GENERIC
            fi
        else
            ocf_log err "Varnish PID file exists, but varnishd is not running"
            return $OCF_ERR_GENERIC
        fi
    fi
    
    return $OCF_NOT_RUNNING
}

varnish_start() {
    local rc
    local backend_options

    varnish_status
    rc=$?
    if [ $rc -eq $OCF_SUCCESS ]; then
        ocf_log info "Varnish already running"
        return $OCF_SUCCESS
    fi

    # check which backend is to be used
    case "$OCF_RESKEY_backend_type" in
       malloc)  
                backend_options="$OCF_RESKEY_backend_size"
                ;;
        file)
                backend_options="$OCF_RESKEY_backend_file,$OCF_RESKEY_backend_size"
                ;;
        *)
                # not implemented yet
                return $OCF_ERR_CONFIGURED 
                ;;
    esac

    # set maximum locked shared memory
    if [ -n "$OCF_RESKEY_max_locked_memory" ]; then
        ocf_log info "Setting max_locked_memory to ${OCF_RESKEY_max_locked_memory}"
        ulimit -l $OCF_RESKEY_max_locked_memory
        u_rc=$?
        if [ "$u_rc" -ne 0 ]; then
           ocf_log warn "Could not set ulimit for locked share memory for Varnish to '$OCF_RESKEY_max_locked_memory'"
        fi
    fi

    # set maximum number of open files
    if [ -n "$OCF_RESKEY_maxfiles" ]; then
        ulimit -n $OCF_RESKEY_maxfiles
        u_rc=$?
        if [ "$u_rc" -ne 0 ]; then
           ocf_log warn "Could not set ulimit for open files for Varnish to '$OCF_RESKEY_maxfiles'"
        fi
    fi

    ocf_run $OCF_RESKEY_binary \
        -P $OCF_RESKEY_pid \
        -a $OCF_RESKEY_listen_address \
        -f $OCF_RESKEY_config \
        -T $OCF_RESKEY_mgmt_address \
        -t $OCF_RESKEY_ttl \
        -u $OCF_RESKEY_varnish_user \
        -g $OCF_RESKEY_varnish_group \
        -p thread_pools=$OCF_RESKEY_thread_pools \
        -p thread_pool_min=$OCF_RESKEY_thread_pool_min \
        -p thread_pool_max=$OCF_RESKEY_thread_pool_max \
        -p thread_pool_timeout=$OCF_RESKEY_thread_pool_timeout \
        -s $OCF_RESKEY_backend_type,$backend_options \
        -S $OCF_RESKEY_secret \
        -n $OCF_RESKEY_name
    rc=$?
    if [ $rc -ne 0 ]; then
        ocf_log err "Varnish failed to start"
        return $OCF_ERR_GENERIC
    fi

    # Spin waiting for varnishd to come up.
    # Let the CRM/LRM time us out if required
    while true; do
        varnish_status
        rc=$?
        [ $rc -eq $OCF_SUCCESS ] && break
        if [ $rc -ne $OCF_NOT_RUNNING ]; then
            ocf_log err "Varnish start failed"
            exit $OCF_ERR_GENERIC
        fi
        sleep 2
    done

    ocf_log info "Varnish started succesfully"
    return $OCF_SUCCESS
}

varnish_stop() {
    local rc
    local pid

    varnish_status
    rc=$?
    if [ $rc -eq $OCF_NOT_RUNNING ]; then
        ocf_log info "Varnish already stopped"
        return $OCF_SUCCESS
    fi

    # kill the varnish process
    pid=$(cat $OCF_RESKEY_pid)
    ocf_run kill -s TERM $pid
    rc=$?

    if [ $rc -ne 0 ]; then
        ocf_log err "Varnish failed to stop"
        return $OCF_ERR_GENERIC
    fi

    # stop waiting
    shutdown_timeout=$((($OCF_RESKEY_CRM_meta_timeout/1000)-5))
    count=0
    while [ $count -lt $shutdown_timeout ]; do
        # check if process still exists
        ocf_run kill -s 0 $pid
        rc=$?
        if [ $rc -ne 0 ]; then
            # Varnish stopped succesfully, so let's delete the pidfile
            rm -f $OCF_RESKEY_pid
            break
        fi
        count=$(expr $count + 1)
        sleep 1
        ocf_log info "Varnish still hasn't stopped yet. Waiting..."
    done 

    varnish_status
    rc=$?
    if [ $rc -ne $OCF_NOT_RUNNING ]; then
        # varnish didn't quit on a SIGTERM, try SIGKILL
        ocf_log warn "Varnish failed to stop after ${shutdown_timeout}s using SIGTERM. Trying SIGKILL..."
        ocf_run kill -s KILL $pid
        # delete the pidfile
        rm -f $OCF_RESKEY_pid
    fi

    ocf_log info "Varnish stopped"
    return $OCF_SUCCESS
}


varnish_validate() {
    if [ -f $OCF_RESKEY_config ]; then
        return $OCF_SUCCESS
    else
        return $OCF_ERR_INSTALLED
    fi
}


case $__OCF_ACTION in
    meta-data)
        meta_data
        exit $OCF_SUCCESS
        ;;
    start)
        varnish_start
        ;;
    stop)
        varnish_stop
        ;;
    monitor|status)
        varnish_status
        ;;
    validate-all)
        varnish_validate
        ;;
    usage|help)
        varnish_usage
        exit $OCF_SUCCESS
        ;;
    *)
        varnish_usage
        exit $OCF_ERR_UNIMPLEMENTED
        ;;
esac
rc=$?
ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc"
exit $rc

